/*
 *  Copyright 2005 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.action.questionnaire.questionnaire;

import com.blandware.atleap.common.Constants;
import com.blandware.atleap.common.QuestionnaireModuleConstants;
import com.blandware.atleap.common.util.StringUtil;
import com.blandware.atleap.model.questionnaire.*;
import com.blandware.atleap.service.exception.OwnerNotFoundException;
import com.blandware.atleap.service.questionnaire.QuestionnaireManager;
import com.blandware.atleap.service.util.MailEngine;
import com.blandware.atleap.webapp.action.core.BaseAction;
import com.blandware.atleap.webapp.form.questionnaire.FillQuestionnaireForm;
import com.blandware.atleap.webapp.util.core.GlobalProperties;
import com.blandware.atleap.webapp.util.core.WebappUtil;
import com.blandware.atleap.webapp.util.questionnaire.QuestionnaireModuleWebConstants;
import org.apache.commons.validator.GenericValidator;
import org.apache.oro.text.regex.*;
import org.apache.struts.action.*;
import org.apache.struts.util.RequestUtils;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;

/**
 * <p>Processes filled questionnaire.</p>
 * <p><a href="FillQuestionnaireAction.java.html"><i>View source</i></a></p>
 *
 * @author Roman Puchkovskiy <a href="mailto:roman.puchkovskiy@blandware.com">
 *         &lt;roman.puchkovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.7 $ $Date: 2006/06/28 16:23:13 $
 * @struts.action name="fillQuestionnaireForm"
 * path="/questionnaire/questionnaire/fill"
 * input="inputForward"
 * validate="false"
 * @struts.action-forward name="inputForward"
 * path=".questionnaire.questionnaire.fill"
 * @struts.action-forward name="listQuestionnairesToFill"
 * path="/questionnaire/questionnaire/listToFill.do"
 * redirect="true"
 */
public final class FillQuestionnaireAction extends BaseAction {
	/**
	 * @param mapping  The ActionMapping used to select this instance
	 * @param form     The optional ActionForm bean for this request (if any)
	 * @param request  The HTTP request we are proceeding
	 * @param response The HTTP response we are creating
	 * @return an ActionForward instance describing where and how
	 *         control should be forwarded, or null if response
	 *         has already been completed
	 */
	public ActionForward execute(ActionMapping mapping, ActionForm form,
	                             HttpServletRequest request,
                                 HttpServletResponse response)
            throws IOException {
        HttpSession session = request.getSession();
        ServletContext servletContext = session.getServletContext();

        String submittedActionKey = QuestionnaireModuleWebConstants.FILL_QUESTIONNAIRE_SUBMITTED_ACTION_KEY;
        if (isCancelled(request)
                || session.getAttribute(submittedActionKey) != null) {
            return mapping.findForward("listQuestionnairesToFill");
        }
        session.setAttribute(submittedActionKey, "fill");

        FillQuestionnaireForm fillQuestionnaireForm = (FillQuestionnaireForm) form;
        Long questionnaireId = null;
        if (fillQuestionnaireForm.getId() != null) {
            questionnaireId = fillQuestionnaireForm.getId();
        }
        if (questionnaireId == null) {
            if (log.isInfoEnabled()) {
                log.info("Missing questionnaire ID, returning to list...");
                return mapping.findForward("listQuestionnairesToFill");
            }
        }

        QuestionnaireManager questionnaireManager = (QuestionnaireManager) getBean(QuestionnaireModuleConstants.QUESTIONNAIRE_MANAGER_BEAN);
        Questionnaire questionnaire = questionnaireManager.retrieveQuestionnaire(questionnaireId);
        if (questionnaire == null) {
			// questionnaire not found. it might be deleted by someone else
			ActionMessages errors = new ActionMessages();
			errors.add("questionnaireNotFound", new ActionMessage("questionnaire.questionnaire.errors.notFound"));
			saveErrors(request, errors);
            return mapping.findForward("listQuestionnairesToFill");
        }

        if (!questionnaire.getActive().booleanValue()) {
			response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
			return null;
        }

        request.setAttribute("questionnaire", questionnaire);

        ActionMessages validationErrors = new ActionMessages();
        boolean requiredErrors = false;
        boolean regexErrors = false;

        Map textQuestionMap = fillQuestionnaireForm.getTextQuestionMap();
        Map intQuestionMap = fillQuestionnaireForm.getIntQuestionMap();
        Map multiIntQuestionMap = fillQuestionnaireForm.getMultiIntQuestionMap();
        Map checkboxQuestionMap = fillQuestionnaireForm.getCheckboxQuestionMap();

        // handling checkboxes
        Map checkboxesMap = buildCheckboxesMap(checkboxQuestionMap);

        // This maps answers to sets of answer options; this map is being filled
        // during cycling through questions to assign answer options to answers
        // after retrieving all answer options from manager
        Map answersToOptionsMap = new HashMap();

        FilledQuestionnaire filledQuestionnaire = new FilledQuestionnaire();
        List questions = new ArrayList(questionnaire.getQuestions());
        for (Iterator i = questions.iterator(); i.hasNext(); ) {
            Question question = (Question) i.next();
            Long questionId = question.getId();
            Set answerOptionIds = new HashSet();
            Long answerId;
            String text = null;
            boolean required = question.getRequired().booleanValue();

            // fill text OR select IDs of checked answer options
            switch (question.getType()) {
            case Question.LINE:
                // fall through
            case Question.MULTILINE:
                text = (String) textQuestionMap.get(questionId.toString());
                if (text == null) {
                    text = "";
                }
                boolean textIsEmpty = GenericValidator.isBlankOrNull(text);
                if (required && textIsEmpty) {
                    String property = "textQuestion(" + questionId + ")";
                    ActionMessage error = new ActionMessage("questionnaire.questionnaire.errors.fillRequiredFields");
                    validationErrors.add(property, error);
                    requiredErrors = true;
                }
                boolean needToValidateWithRegex = true;
                if (!required && textIsEmpty) {
                    needToValidateWithRegex = false;
                }
                if (needToValidateWithRegex
                        && !GenericValidator.isBlankOrNull(question.getValidationRegex())) {
                    PatternCompiler compiler = new Perl5Compiler();
                    PatternMatcher matcher = new Perl5Matcher();
                    Pattern pattern;
                    try {
                        pattern = compiler.compile(question.getValidationRegex());
                    } catch (MalformedPatternException e) {
                        pattern = null;
                    }
                    if (pattern != null && !matcher.matches(text, pattern)) {
                        String property = "textQuestion(" + questionId + ")";
                        ActionMessage error = new ActionMessage("questionnaire.questionnaire.errors.illegalText");
                        validationErrors.add(property, error);
                        regexErrors = true;
                    }
                }
                break;
            case Question.DROPDOWN_LIST:
                text = null;
                answerId = (Long) intQuestionMap.get(questionId.toString());
                boolean selected = answerId != null && answerId.intValue() != QuestionnaireModuleWebConstants.NOTHING_SELECTED_OPTION_ID;
                if (selected) {
                    answerOptionIds.add(answerId);
                }
                if (required && !selected) {
                    String property = "intQuestion(" + questionId + ")";
                    ActionMessage error = new ActionMessage("questionnaire.questionnaire.errors.fillRequiredFields");
                    validationErrors.add(property, error);
                    requiredErrors = true;
                }
                break;
             case Question.RADIO_BUTTONS:
                text = null;
                answerId = (Long) intQuestionMap.get(questionId.toString());
                if (answerId != null) {
                    answerOptionIds.add(answerId);
                }
                if (required && answerId == null) {
                    String property = "intQuestion(" + questionId + ")";
                    ActionMessage error = new ActionMessage("questionnaire.questionnaire.errors.fillRequiredFields");
                    validationErrors.add(property, error);
                    requiredErrors = true;
                }
                break;
            case Question.CHECK_BOXES:
                text = null;
                Set answerOptionIdSet = (Set) checkboxesMap.get(questionId);
                if (answerOptionIdSet != null) {
                    answerOptionIds.addAll(answerOptionIdSet);
                }
                if (required && (answerOptionIdSet == null || answerOptionIdSet.isEmpty())) {
                    String property = "checkboxQuestion(" + questionId + ")";
                    ActionMessage error = new ActionMessage("questionnaire.questionnaire.errors.fillRequiredFields");
                    validationErrors.add(property, error);
                    requiredErrors = true;
                }
                break;
            case Question.MULTILIST:
                text = null;
                Long[] answerOptionIdArray = (Long[]) multiIntQuestionMap.get(questionId.toString());
                if (answerOptionIdArray != null) {
                    for (int j = 0; j < answerOptionIdArray.length; j++) {
                        answerOptionIds.add(answerOptionIdArray[j]);
                    }
                }
                if (required && (answerOptionIdArray == null || answerOptionIdArray.length == 0)) {
                    String property = "multiIntQuestion(" + questionId + ")";
                    ActionMessage error = new ActionMessage("questionnaire.questionnaire.errors.fillRequiredFields");
                    validationErrors.add(property, error);
                    requiredErrors = true;
                }
                break;
            }

            Answer answer = new Answer();
            answer.setText(text);
            filledQuestionnaire.addAnswer(answer);
            question.addAnswer(answer);
            for (Iterator j = answerOptionIds.iterator(); j.hasNext(); ) {
                Long answerOptionId = (Long) j.next();
                AnswerOption answerOption = questionnaireManager.retrieveAnswerOption(answerOptionId);
                boolean idIsValid = answerOption != null
                        && (questionId.equals(answerOption.getQuestion().getId())
                        && questionnaireId.equals(answerOption.getQuestion()
                                                  .getQuestionnaire()
                                                  .getId()));
                if (idIsValid) {
                    Set options = (Set) answersToOptionsMap.get(answer);
                    if (options != null) {
                        options.add(answerOption);
                    } else {
                        options = new HashSet();
                        options.add(answerOption);
                        answersToOptionsMap.put(answer, options);
                    }
                }
            }
        }

        if (requiredErrors) {
            validationErrors.add("fillRequiredFields", new ActionMessage("questionnaire.questionnaire.errors.fillRequiredFields"));
        }
        if (regexErrors) {
            validationErrors.add("illegalText", new ActionMessage("questionnaire.questionnaire.errors.illegalText"));
        }
        if (requiredErrors || regexErrors) {
            session.removeAttribute(submittedActionKey);
            saveErrors(request, validationErrors);
            return mapping.getInputForward();
        }

        for (Iterator i = answersToOptionsMap.keySet().iterator(); i.hasNext(); ) {
            Answer answer = (Answer) i.next();
            Set options = (Set) answersToOptionsMap.get(answer);
            if (options != null) {
                for (Iterator j = options.iterator(); j.hasNext(); ) {
                    AnswerOption answerOption = (AnswerOption) j.next();
                    answer.addAnswerOption(answerOption);
                }
            }
        }

        try {
            questionnaireManager.createFilledQuestionnaire(filledQuestionnaire,
                                                           questionnaireId);
        } catch (OwnerNotFoundException e) {
			// questionnaire not found. it might be deleted by someone else
			ActionMessages errors = new ActionMessages();
			errors.add("ownerNotFound", new ActionMessage("questionnaire.filledQuestionnaire.errors.ownerNotFound"));
			saveErrors(request, errors);
			return mapping.findForward("listQuestionnairesToFill");
        }

        // send via email
        sendEmail(servletContext, request, filledQuestionnaire, questionnaire);

        ActionMessages messages = new ActionMessages();
        messages.add("success", new ActionMessage("questionnaire.questionnaire.messages.fillSuccessful"));
        saveMessages(request, messages);

        session.removeAttribute(submittedActionKey);

        return mapping.findForward("listQuestionnairesToFill");
    }

    /**
     * Sends e-mail with a filled questionnaire.
     *
     * @param servletContext servlet context
     * @param request request
     * @param filledQuestionnaire filled questionnaire
     * @param questionnaire questionnaire which was willed
     */
    private void sendEmail(ServletContext servletContext,
                           HttpServletRequest request,
                           FilledQuestionnaire filledQuestionnaire,
                           Questionnaire questionnaire) {
        GlobalProperties globals = GlobalProperties.getInstance(servletContext);
        String localeIdentifier = globals.getString(QuestionnaireModuleConstants.LANGUAGE_PROPERTY);
        if ( GenericValidator.isBlankOrNull(localeIdentifier) ) {
            localeIdentifier = (RequestUtils.getUserLocale(request, null)).getLanguage();
        }

        List answers = new ArrayList();
        Map answerMap;
        for (int i = 0; i < filledQuestionnaire.getAnswers().size(); i++) {
            answerMap = new HashMap();
            Answer answer = (Answer) filledQuestionnaire.getAnswers().get(i);
            Question question = answer.getQuestion();
            String questionTitle = WebappUtil.getFieldValue(question.getTitle(),
                                                            localeIdentifier,
                                                            request, true);
            String text = "";
            List options = new ArrayList();
            if (question.isTextual()) {
                text = answer.getText();
            }
            if (question.isMultiOption()) {
                for (Iterator j = answer.getAnswerOptions().iterator(); j.hasNext();) {
                    AnswerOption option = (AnswerOption) j.next();
                    String optionTitle = WebappUtil.getFieldValue(option.getTitle(),
                            localeIdentifier, request, true);
                    options.add(StringUtil.htmlEncode(optionTitle));
                }
            }
            answerMap.put("question", questionTitle);
            answerMap.put("text", StringUtil.htmlEncode(text));
            answerMap.put("options", options);
            answers.add(answerMap);
        }

        String questionnaireTitle = WebappUtil.getFieldValue(questionnaire.getTitle(),
                                                             localeIdentifier,
                                                             request, true);

        Map model = new HashMap();
        model.put("answers", answers);
        model.put("questionnaire", StringUtil.htmlEncode(questionnaireTitle));

        MailEngine mailEngine = (MailEngine) getBean(Constants.MAIL_ENGINE_BEAN);
        String address = globals.getString(QuestionnaireModuleConstants.ADDRESS_PROPERTY);
        String fromAddress = address;

        try {
            mailEngine.sendMessage(fromAddress, null, new String[]{address},
                                   null, null, "filledQuestionnaire",
                                   localeIdentifier, model, false);
        } catch ( Exception e ) {
            // log error
            if ( log.isErrorEnabled() ) {
                StringWriter sw = new StringWriter();
                e.printStackTrace(new PrintWriter(sw));
                log.error(sw.toString());
            }
        }
    }

    /**
     * Builds a map from question ID to set of option IDs.
     *
     * @param checkboxQuestionMap map from ids of form QestionID_OptionID
     * to 'on'
     * @return built map
     */
    private Map buildCheckboxesMap(Map checkboxQuestionMap) {
        String delimiter = QuestionnaireModuleWebConstants.CHECKBOX_ID_DELIMITER;
        Map checkboxesMap = new HashMap();
        for (Iterator i = checkboxQuestionMap.keySet().iterator(); i.hasNext(); ) {
            String key = (String) i.next();
            Long questionId = null;
            Long answerOptionId = null;
            if (!GenericValidator.isBlankOrNull(key)) {
                int index = key.indexOf(delimiter);
                if (index != -1) {
                    try {
                        questionId = Long.valueOf(key.substring(0, index));
                        answerOptionId = Long.valueOf(key.substring(index + 1));
                    } catch (NumberFormatException e) {
                        questionId = null;
                        answerOptionId = null;
                    }
                }
            }
            if (questionId != null && answerOptionId != null) {
                Set alreadyAdded = (Set) checkboxesMap.get(questionId);
                if (alreadyAdded != null) {
                    alreadyAdded.add(answerOptionId);
                } else {
                    Set set = new HashSet();
                    set.add(answerOptionId);
                    checkboxesMap.put(questionId, set);
                }
            }
        }
        return checkboxesMap;
    }
}
